package com.huawei.openGauss;

import java.io.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.Random;


public class WorkLoadManager {
    public static String delayLog;
    public static String tpsLog;
    public static String commitLog;
    public static int loopNum;
    public static int[] rates;
    public static int buffer;
    public static String URL;
    public static String username;
    public static String password;
    public static int numberCount = 0;
    public static String tableName;
    public static String tableSpace;
    public static int totalTrans;
    public static int tableNum;
    public static SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
    public static int threadCount;
    public static String actionMode;
    public static int tpsMax = 0;
    public static boolean autoCommit;
    public static String showContext;

    public WorkLoadManager() throws Exception {
        String configFile = "config.properties";
        InputStream in = new FileInputStream(configFile);
        Properties prop = new Properties();
        prop.load(in);

        delayLog = prop.getProperty("delayLog");
        tpsLog = prop.getProperty("tpsLog");
        commitLog = prop.getProperty("commitLog");
        loopNum = Integer.parseInt(prop.getProperty("loopNum"));
        buffer = Integer.parseInt(prop.getProperty("buffer"));
        URL = prop.getProperty("url");
        username = prop.getProperty("username");
        password = prop.getProperty("password");
        tableName = prop.getProperty("tableName");
        tableSpace = prop.getProperty("tableSpace");
        tableNum = Integer.parseInt(prop.getProperty("tableNum"));
        threadCount = Integer.parseInt(prop.getProperty("thread"));
        actionMode = prop.getProperty("actionMode");
        autoCommit = Boolean.parseBoolean(prop.getProperty("autoCommit"));
        showContext = prop.getProperty("showContext");

        if (autoCommit && showContext.equals("commitDelay")) {
            System.out.println("When autoCommit is set to true, the commitDelay information cannot be printed!");
            throw new Exception();
        }

        String ratesStr = prop.getProperty("rate");
        String[] ratesGrp = ratesStr.split(",");
        rates = new int[ratesGrp.length];
        for (int i = 0; i < rates.length; i++) {
            rates[i] = Integer.parseInt(ratesGrp[i]);
        }
    }

    public static synchronized void appendMethod(String fileName, String content) {
        try {
            FileWriter writer = new FileWriter(fileName, true);
            writer.write(content + "\r\n");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void printAvgTps() throws IOException {
        int tpsTime = 0;
        int tpsTotal = 0;
        File file = new File(tpsLog);
        if (file.exists()) {
            InputStreamReader reader = new InputStreamReader(new FileInputStream(file));
            BufferedReader br = new BufferedReader(reader);
            String line = br.readLine();
            if (line.equals("timestamp,tps")) {
                line = br.readLine();
            }
            while (line != null) {
                tpsTime++;
                tpsTotal += Integer.parseInt(line.split(",")[1]);
                line = br.readLine();
            }
            reader.close();
            if (tpsTime == 0) {
                System.out.println("Please increase the data volume.");
            } else {
                if (showContext.equals("tps")) {
                    System.out.println("Total time: " +  tpsTime);
                    System.out.println("MAX tps:" + tpsMax);
                    System.out.println("Average tps: " + tpsTotal / tpsTime);
                }
            }
        }

    }

    private static void deleteFiles(String sPath) {
        File file = new File(sPath);
        if (file.exists() && file.isFile()) {
            //noinspection ResultOfMethodCallIgnored
            file.delete();
        }
    }

    public static void main(String[] args) {

        try {
            //noinspection InstantiationOfUtilityClass
            new WorkLoadManager();

            deleteFiles(delayLog);
            deleteFiles(tpsLog);
            deleteFiles(commitLog);

            WorkLoadManager.appendMethod(delayLog, "timestamp,insert(us),update(us),delete(us),query(us)");
            WorkLoadManager.appendMethod(tpsLog, "timestamp,tps");
            WorkLoadManager.appendMethod(commitLog, "timestamp,commit(ms)");

            for (int i = 1; i <= tableNum; i++) {
                KeySequence ks = new KeySequence();
                TimeMonitor tm = new TimeMonitor();
                ks.init(i);
                for (int j = 0; j < threadCount / tableNum; j++) {
                    new loadRunner(ks, tm, loopNum, i).start();
                    //noinspection BusyWait
                    Thread.sleep(100);
                }
            }

            Thread.sleep(1000);

            while (true) {
                if (numberCount != 0) {
                    try {
                        int totalTrans_pre = totalTrans;
                        //noinspection BusyWait
                        Thread.sleep(1000);
                        int tps = totalTrans - totalTrans_pre;
                        if (tps > tpsMax) {
                            tpsMax = tps;
                        }
                        if (showContext.equals("tps")) {
                            System.out.println(df.format(new Date()) + "\t" + tps);
                        }
                        WorkLoadManager.appendMethod(tpsLog, df.format(new Date()) + "," + tps);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }

            printAvgTps();

            System.out.println("End");
        } catch (Exception e) {
            System.out.println("Run WorkLoadManager failed.");
        }
    }

    public static class KeySequence {

        private int key = 0;
        private int currentKey = 0;

        public void init(int suffix) {
            try {
                Class.forName("org.postgresql.Driver");
                Connection conn = DriverManager.getConnection(URL, username, password);
                System.out.println("init");
                Statement stmt = conn.createStatement();
                String tableNameWithSuffix = tableName + "_" + suffix;
                String pkeyIndexNameWithSuffix = tableName + "_pkey_" + suffix;
                String sqlString;
                if (actionMode.equals("prepare")) {
                    try {
                        sqlString = String.format("drop table if exists %s", tableNameWithSuffix);
                        stmt.executeUpdate(sqlString);
                        sqlString = String.format("create table %s (cust_no character varying(14) not null, " +
                                "cust_status character varying(6) not null, " +
                                "create_time timestamp without time zone default now(), " +
                                "update_time timestamp without time zone default now())", tableNameWithSuffix);
                        stmt.executeUpdate(sqlString);
                        sqlString = String.format("alter table %s add constraint %s " +
                                "primary key (cust_no)", tableNameWithSuffix, pkeyIndexNameWithSuffix);
                        stmt.executeUpdate(sqlString);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                sqlString = String.format("select count(*) from %s", tableNameWithSuffix);
                ResultSet rs = stmt.executeQuery(sqlString);
                rs.next();
                this.currentKey = this.key = rs.getInt(1);
                rs.close();
                stmt.close();
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public synchronized int nextBufKey() {
            this.currentKey = Math.max(this.key - threadCount / tableNum * WorkLoadManager.buffer, 0);
            this.key = this.key + WorkLoadManager.buffer;
            return this.key;
        }

        public int getKey() {
            Random r = new Random();
            return r.nextInt(this.currentKey + 1);
        }

        public static String getRandomString(int length) {
            String str = "0123456789";
            Random random = new Random();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < length; i++) {
                int number = random.nextInt(10);
                sb.append(str.charAt(number));
            }
            return sb.toString();
        }

        public static Timestamp getCurrentTimeStamp() {
            Date today = new Date();
            return new Timestamp(today.getTime());
        }

    }

    @SuppressWarnings("unused")
    public static class TimeMonitor {

        private long totalInstTime;

        private long totalUptTime;

        private long totalDelTime;

        private long totalQueryTime;

        public long getTotalInstTime() {
            return totalInstTime;
        }

        public long getTotalUptTime() {
            return totalUptTime;
        }

        public long getTotalDelTime() {
            return totalDelTime;
        }

        public long getTotalQueryTime() {
            return totalQueryTime;
        }

        public long getCurrentTime() {
            return System.currentTimeMillis();
        }

        public long getCurrentTimeNano() {
            return System.nanoTime();
        }

        public synchronized void calTotalInstTime(long time) {
            totalInstTime = totalInstTime + time;
        }

        public synchronized void calTotalUptTime(long time) {
            totalUptTime = totalUptTime + time;
        }

        public synchronized void calTotalDelTime(long time) {
            totalDelTime = totalDelTime + time;
        }

        public synchronized void calTotalQueryTime(long time) {
            totalQueryTime = totalQueryTime + time;
        }
    }

    public static class loadRunner extends Thread {

        private Connection conn = null;
        private PreparedStatement stmt_inst = null;
        private PreparedStatement stmt_updt = null;
        private PreparedStatement stmt_del = null;
        private PreparedStatement stmt_sel = null;
        final private KeySequence ks;
        final private TimeMonitor tm;
        final private int loopNum;
        final private String tableNameWithSuffix;

        public loadRunner(KeySequence ks, TimeMonitor tm, int loopNum, int suffix) {
            this.ks = ks;
            this.tm = tm;
            this.loopNum = loopNum;
            this.tableNameWithSuffix = tableName + "_" + suffix;
        }

        public void run() {
            numberCount = numberCount + 1;
            int workLoadIndex = numberCount;
            try {
                System.out.printf("WorkerLoad %d Start.%n", workLoadIndex);
                try {
                    conn = DriverManager.getConnection(URL, username, password);
                } catch (Exception e) {
                    e.printStackTrace();
                    sleep(5000);
                }
                conn.setAutoCommit(autoCommit);
                stmt_inst = conn.prepareStatement(String.format("insert into %s (cust_no, cust_status) values (?, ?)", tableNameWithSuffix));
                stmt_updt = conn.prepareStatement(String.format("update %s set cust_status=?, update_time=? where cust_no=?", tableNameWithSuffix));
                stmt_del = conn.prepareStatement(String.format("delete from %s where cust_no=?", tableNameWithSuffix));
                stmt_sel = conn.prepareStatement(String.format("select * from %s where cust_status=?", tableNameWithSuffix));
                int i = 0;
                long totalInstTime = 0;
                long totalUptTime = 0;
                long totalDelTime = 0;
                long totalQueryTime = 0;
                int startKey = 0;
                int endKey = 0;
                String context = "";
                while (i < loopNum || loopNum == 0) {
                    Thread.sleep(1);
                    long startTime;
                    long endTime;
                    long instTime = 0;
                    long uptTime = 0;
                    long delTime = 0;
                    long queryTime = 0;
                    double commitTime;
                    int j0 = rates[0];
                    int j1 = rates[1];
                    int j2 = rates[2];
                    int j3 = rates[3];
                    long insertValue = 0;
                    long updateValue = 0;
                    long deleteValue = 0;
                    long selectValue = 0;
                    try {
                        while (j0 > 0 || j1 > 0 || j2 > 0 || j3 > 0) {
                            if (j0 > 0) {
                                if (startKey == endKey) {
                                    endKey = ks.nextBufKey();
                                    startKey = endKey - buffer;
                                }
                                stmt_inst.setString(1, String.format("%014d", startKey));
                                stmt_inst.setString(2, KeySequence.getRandomString(6));
                                startTime = tm.getCurrentTimeNano();
                                insertValue = insertValue + stmt_inst.executeUpdate();
                                endTime = tm.getCurrentTimeNano();
                                totalTrans = totalTrans + 1;
                                instTime = (endTime - startTime) / 1000;
                                totalInstTime = totalInstTime + instTime;
                                startKey = startKey + 1;
                                j0 = j0 - 1;
                            }
                            if (j1 > 0) {
                                int updateKey = ks.getKey();
                                stmt_updt.setString(1, KeySequence.getRandomString(6));
                                stmt_updt.setTimestamp(2, KeySequence.getCurrentTimeStamp());
                                stmt_updt.setString(3, String.format("%014d", updateKey));
                                startTime = tm.getCurrentTimeNano();
                                updateValue = updateValue + stmt_updt.executeUpdate();
                                endTime = tm.getCurrentTimeNano();
                                totalTrans = totalTrans + 1;
                                uptTime = (endTime - startTime) / 1000;
                                totalUptTime = totalUptTime + uptTime;
                                j1 = j1 - 1;
                            }
                            if (j2 > 0) {
                                int deleteKey = ks.getKey();
                                stmt_del.setString(1, String.format("%014d", deleteKey));
                                startTime = tm.getCurrentTimeNano();
                                deleteValue = deleteValue + stmt_del.executeUpdate();
                                endTime = tm.getCurrentTimeNano();
                                totalTrans = totalTrans + 1;
                                delTime = (endTime - startTime) / 1000;
                                totalDelTime = totalDelTime + delTime;
                                j2 = j2 - 1;
                            }
                            if (j3 > 0) {
                                stmt_sel.setString(1, KeySequence.getRandomString(6));
                                startTime = tm.getCurrentTimeNano();
                                ResultSet resultSet = stmt_sel.executeQuery();
                                endTime = tm.getCurrentTimeNano();
                                while (resultSet.next()) {
                                    selectValue = selectValue + 1;
                                }
                                totalTrans = totalTrans + 1;
                                queryTime = (endTime - startTime) / 1000;
                                totalQueryTime = totalQueryTime + queryTime;
                                j3 = j3 - 1;
                            }
                            if (context.equals("")) {
                                context = String.format("%d,%d,%d,%d", instTime, uptTime, delTime, queryTime);
                            } else {
                                context = String.format("%s\n%d,%d,%d,%d", context, instTime, uptTime, delTime, queryTime);
                            }
                            instTime = -1;
                            uptTime = -1;
                            delTime = -1;
                            queryTime = -1;
                        }
                        if (i % buffer == 0 || i == loopNum - 1) {
                            WorkLoadManager.appendMethod(delayLog, context);
                            context = "";
                        }
                        if (!autoCommit) {
                            startTime = tm.getCurrentTimeNano();
                            conn.commit();
                            endTime = tm.getCurrentTimeNano();
                            commitTime = (double) (endTime - startTime) / 1000000;
                            WorkLoadManager.appendMethod(commitLog, df.format(new Date()) + String.format(",%.3f", commitTime));
                            if (showContext.equals("commitDelay")) {
                                System.out.println(df.format(new Date()) + String.format("\t %4.3fms\tWorkLoad%d\tinsertNum %3d\tupdateNum %3d\tdeleteNum %3d\tselectNum %3d", commitTime, workLoadIndex, insertValue, updateValue, deleteValue, selectValue));
                            }
                        }
                    } catch (Exception e) {
                        if (conn != null) {
                            System.out.println(e.getMessage());
                            System.out.printf("WorkLoad%s: Some mistake is occurred in this loop.%n\n", workLoadIndex);
                            if (!autoCommit) {
                                conn.rollback();
                            }
                        }
                    } finally {
                        i = i + 1;
                    }
                }
                tm.calTotalInstTime(totalInstTime);
                tm.calTotalUptTime(totalUptTime);
                tm.calTotalDelTime(totalDelTime);
                tm.calTotalQueryTime(totalQueryTime);
            } catch (Exception e) {
                System.out.println(e.getMessage());
                this.run();
            } finally {
                try {
                    stmt_updt.close();
                    stmt_inst.close();
                    stmt_del.close();
                    stmt_sel.close();
                    conn.setAutoCommit(true);
                    conn.close();
                    System.out.printf("WorkerLoad %d end.%n", workLoadIndex);
                    numberCount = numberCount - 1;
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
